/*
FileSystem.cpp
 */

#include "Cache.h"
#include "File.h"
#include "DataBlock.h"
#include "FileSystem.h"

FileSystem::FileSystem() :
fs_file(NULL),
cache(NULL),
fs_num(0) {

    unsigned int i;

    //allocate inodes
    inodes = (Inode **) malloc(Settings::NumInodes * sizeof (Inode *));
    for (i = 0; i < Settings::NumInodes; i++) {
        inodes[i] = (Inode *) malloc(sizeof (Inode));
    }

    //The superblock contains a char for each data block.
    superblock = (char *) malloc(Settings::NumDataBlocks);

}

FileSystem::~FileSystem() {

    unsigned int i;
    map < unsigned int, File * >::iterator it;
    
    //close open files
    for(it=open_files.begin();it!=open_files.end();it++){
        Close(it->first);
    }

    //free inodes
    for (i = 0; i < Settings::NumInodes; i++) {
        free(inodes[i]);
    }
    free(inodes);

    free(superblock);
    if (fs_file != NULL) {
        fclose(fs_file);
    }

    if (cache != NULL) {
        delete cache;
    }

}

/*
Desc:
    Stops working with the previous file system and creates a new one.
Returns:
    0 on success
    -1 on error
 */
int FileSystem::Mkfs() {

    unsigned int i;
    unsigned int r;
    Directory * tmp_dir;
    stringstream ss;
    string str_fs;

    if (fs_file != NULL) {
        //close the previous file
        fclose(fs_file);
    }

    if (cache != NULL) {
        //we will start the new file system with a fresh cache
        delete cache;
    }
    cache = new Cache(this);

    //set that all the data blocks are free
    bzero(superblock, Settings::NumDataBlocks);

    //the file system is stored in a file called FS# where '#' is an int
    //find the smallest unused #
    ss << "FS" << fs_num;
    fs_num++;
    str_fs = ss.str();
    fs_file = fopen(str_fs.c_str(), "w+");
    if (fs_file == NULL) {
        printf( "Error FileSystem::Mkfs - could not create %s\n", str_fs.c_str());
        return -1;
    }
    printf("FileSystem::Mkfs - creating %s\n", str_fs.c_str());

    //write the superblock to the file
    r = write_superblock();
    if (r != 0) {
        return -1;
    }

    //write the inodes to the file
    for (i = 0; i < Settings::NumInodes; i++) {
        //re-initializes all the inode's values to defaults, sets this inode as 'free' so that it can be used for a new file
        r = set_inode_to_free(i);
        if (r != 0) {
            return -1;
        }
    }

    //create the root directory
    tmp_dir = create_new_directory();
    if (tmp_dir == NULL) {
        return -1;
    }
    cur_dir_inode_id = root_dir_inode_id = tmp_dir->inode_id;
    delete tmp_dir;

    return 0;
}

/*
Returns:
    -1 on error
    the file descriptor on error
*/
int FileSystem::Open(string filename, string flag) {

    unsigned int inode_id;
    int r;
    File* f;
    int nextindex = 0;

    map<unsigned int, File*>::iterator it;

    r = inode_id_from_name(filename, &inode_id);

    if(r == 0){
        //file exists, make sure it is a file and not a directory
        if(inodes[inode_id]->free0_file1_dir2 != 1){
            cout << "FAILURE to open " << filename << " - it is not a file (it may be a directory)" << endl;
            return -1;
        }
        //make sure the file is not already open
        for (it = open_files.begin(); it != open_files.end(); it++) {
            if(it->second != NULL && it->second->inode_id == inode_id){
                cout << "FAILURE to open " << filename << " - it is already open" << endl;
                return -1;
            }
        }
    }    

    if (r == 0 || flag.compare("w") == 0 || flag.compare("rw") == 0 || flag.compare("wr") == 0) {
        f = FileSystem::create_or_get_file(filename);
    } else {
        cout << "FAILURE to open the file: " << filename << " with flags " << flag << endl;
        return -1;
    }

    //find lowest unused key index
    for (it = open_files.begin(); it != open_files.end(); it++) {
        if ((*it).second == NULL) {
            (*it).second = f;
            open_files_permissions[(*it).first] = flag;
            return (*it).first;
        }
    }

    nextindex = open_files.size();
    open_files[nextindex] = f;
    open_files_permissions[nextindex] = flag;
    return nextindex;
}

/*
Reads size bytes into buf
Returns the number of bytes read, or -1 on error
 */
int FileSystem::Read(unsigned int fd, unsigned int size) {
    File* f;
    unsigned int fileread;

    buf = (char *) malloc(size+1 * sizeof (char));

    map<unsigned int, File*>::iterator it;
    string flag = open_files_permissions[fd];

    if (flag.compare("r") != 0 && flag.compare("rw") != 0 && flag.compare("wr") != 0) {
        free(buf);
        cout << "Error: Flag is " << flag << endl;
        return -1;
    }

    if (open_files.count(fd) > 0) {
        it = open_files.find(fd);

        f = (*it).second;

        if (f != NULL) {
            fileread = f->Read(size, buf);
            if (fileread>0) {
                buf[fileread] = '\0';
                cout << buf << endl;
                free(buf);
            } else{
                free(buf);
            }
        } else {
            free(buf);
            return -1;
        }
    } else {
        free(buf);
        return -1;
    }

    return fileread;
}

/*
Writes the string into the file
Returns the number of bytes written, or -1 on error
*/
int FileSystem::Write(unsigned int fd, string str) {

    File* f;
    unsigned int filewrite;
    map<unsigned int, File*>::iterator it;

    string flag = open_files_permissions[fd];

    if (flag.compare("w") != 0 && flag.compare("rw") != 0 && flag.compare("wr") != 0) {
        cout << "Error: Flag is " << flag << endl;
        return -1;
    }

    if (open_files.count(fd) > 0) {
        it = open_files.find(fd);

        f = (*it).second;

        if (f != NULL) {
            filewrite = f->Write(str.size(), (char *)str.c_str());
        } else {
            return -1;
        }
    } else {
        return -1;
    }

    return filewrite ;
}

int FileSystem::Seek(unsigned int fd, unsigned int offset) {
    File* f;

    map<unsigned int, File*>::iterator it;

    if (open_files.count(fd) > 0) {
        it = open_files.find(fd);

        f = (*it).second;

        if (f != NULL) {
            f->Seek(offset);
        } else {
            return -1;
        }
    } else {
        return -1;
    }

    return 0;
}

int FileSystem::Close(unsigned int fd) {

    if (open_files.count(fd) == 1) {
        delete open_files[fd];
        open_files[fd] = NULL;
        return 0;
    } else {
        return -1;
    }
}

/*
Not used to create non-root directories
 */
int FileSystem::MkDir(string str_dir) {

    int r;
    int i;
    string str_new_dir;
    string str_parent_dir;
    Directory * new_dir;
    Directory * parent_dir; //the directory that the new directory goes in

    //manipulate the directory name passed in to get the directory that the new directory will be put into

    //cout << "Creating " << str_dir << endl;

    i = split_parent_child(str_dir, str_parent_dir, str_new_dir);
    if (i != 0) {
        return -1;
    }

    //get the existing directory that the new directory will be put into
    //cout << "Parent directory: '" << str_parent_dir << "'" << endl;
    parent_dir = get_dir(str_parent_dir);
    if (parent_dir == NULL) {
        return -1;
    }

    //create new directory (give it an inode)
    new_dir = create_new_directory();
    if (new_dir == NULL) {
        delete parent_dir;
        return -1;
    }

    //writes to the data block of the parent
    i = parent_dir->AddEntry(str_new_dir, new_dir->inode_id);
    if (i != 0) {
        delete parent_dir;
        delete new_dir;
        return -1;
    }

    //allow going backwards to the parent ../
    r = new_dir->AddEntry("..", parent_dir->inode_id);

    delete new_dir;
    delete parent_dir;

    return r;
}

int FileSystem::RmDir(string str_dir) {

    string str_new_dir;
    string str_parent_dir;
    Directory *to_rmdir;
    Directory *parent_dir;
    unsigned int inode_id;
    int i, r;

    r = inode_id_from_name(str_dir, &inode_id);
    if (r != 0) {
        printf( "Error FileSystem::RmDir - failed to remove '%s': No such file or directory\n", str_dir.c_str());
        return -1;
    }

    /*
     * Make sure I'm not deleting myself.
     */
    if (inode_id == cur_dir_inode_id) {
        printf( "Error FileSystem::RmDir - We are currently in this directory cannot delete.\n");
        return -1;
    }

    /*
     * Setup so we can check if the directory we are trying to remove is empty
     */
    to_rmdir = new Directory(this, inode_id);
    /* 
     * Check that directory is empty. If value of entries is greater than 1 then not empty
     */
    if (to_rmdir->entries.size() > 1) {
        printf( "Error FileSystem::RmDir - failed to remove `%s': Directory not empty\n", str_dir.c_str());
        delete to_rmdir;
        return -1;
    }

    i = split_parent_child(str_dir, str_parent_dir, str_new_dir);
    if (i != 0) {
        delete to_rmdir;
        return -1;
    }

    //remove the '..' entry from the parent directory so that the parent link count decrements
    i = to_rmdir->RemoveEntry("..");
    if (i != 0) {
        delete to_rmdir;
        return -1;
    }

    //remove the entry from the parent directory
    parent_dir = get_dir(str_parent_dir);
    if (parent_dir == NULL) {
        delete to_rmdir;
        return -1;
    }
    i = parent_dir->RemoveEntry(str_new_dir);
    delete parent_dir;
    if (i != 0) {
        delete to_rmdir;
        return -1;
    }

    //free the inode and data blocks associated with this directory
    i = to_rmdir->FinalUnlink();
    delete to_rmdir;
    if (i != 0) {
        return -1;
    }

    return 0;
}

int FileSystem::Cd(string str_dir) {
    unsigned int inode_id;
    int r;

    //    cout << "Before call\n";
    r = inode_id_from_name(str_dir, &inode_id);

    if (r == -1) {
        printf( "cd: %s: No such file or directory\n", str_dir.c_str());
        return -1;
    } else {
        if (inodes[inode_id]->free0_file1_dir2 == 2) {
            cur_dir_inode_id = inode_id;
        } else {
            printf( "cd: %s: Not a directory\n", str_dir.c_str());
        }
        //        cout << "help" << endl;
        //        cout << "Current inode is " << cur_dir_inode_id << endl;
    }
    return 0;
}

int FileSystem::Link(string src, string dest) {

    int i, j;
    string str_src_parent_dir;
    string str_src_object;
    string str_dest_parent_dir;
    string str_dest_object;
    Directory * dest_parent_dir;
    unsigned int inode_id;

    i = split_parent_child(src, str_src_parent_dir, str_src_object);
    if (i != 0) {
        return -1;
    }

    j = split_parent_child(dest, str_dest_parent_dir, str_dest_object);
    if (j != 0) {
        return -1;
    }

    i = inode_id_from_name(src, &inode_id);
    if (i != 0) {
        printf( "Error FileSystem::Link - source file does not exist ('%s')\n", src.c_str());
        return -1;
    }

    if (inodes[inode_id]->free0_file1_dir2 != 1) {
        printf( "Error FileSystem::Link - source is not a file: ('%s')\n", src.c_str());
        return -1;
    }

    dest_parent_dir = get_dir(str_dest_parent_dir);
    if (dest_parent_dir == NULL) {
        return -1;
    }

    i = dest_parent_dir->AddEntry(str_dest_object, inode_id);
    delete dest_parent_dir;
    if (i != 0) {
        return -1;
    }

    return 0;
}

int FileSystem::UnLink(string filename) {

    int i;
    string str_parent_dir;
    string str_object;
    Directory * parent_dir;
    unsigned int inode_id;
    File * f;

    i = split_parent_child(filename, str_parent_dir, str_object);
    if (i != 0) {
        return -1;
    }

    i = inode_id_from_name(filename, &inode_id);
    if (i != 0) {
        printf( "Error FileSystem::Unlink - file does not exist ('%s')\n", filename.c_str());
        return -1;
    }

    if (inodes[inode_id]->free0_file1_dir2 != 1) {
        printf( "Error FileSystem::Unlink - target is not a file ('%s')\n", filename.c_str());
        return -1;
    }

    parent_dir = get_dir(str_parent_dir);
    if (parent_dir == NULL) {
        return -1;
    }

    i = parent_dir->RemoveEntry(str_object);
    delete parent_dir;
    if (i != 0) {
        return -1;
    }

    if (inodes[inode_id]->link_count == 0) {

        //This file is ready to be deleted from our file system
        f = new File(this, inode_id);

        //allows the data blocks and inodes to be re-used
        i = f->FinalUnlink();
        delete f;
        if (i != 0) {
            return -1;
        }

    }

    return 0;
}

int FileSystem::Stat(string filename) {

    Inode *inode;
    unsigned int inode_id;
    int r;
    struct tm * timeinfo;
    time_t rawtime;
    char cstr_time[100];

    if ((filename.length() == 1) && (filename[0] == '/')) {
        inode_id = root_dir_inode_id;

    } else {

        r = inode_id_from_name(filename, &inode_id);

        if (r == -1) {
            printf( "stat: cannot stat '%s': No such file or directory\n", filename.c_str());
            return -1;
        }
    }

    inode = inodes[inode_id];
    cout << "\tFile: '" << filename << "'" << endl;
    cout << "\tSize: " << inode->size << "\tBlocks: " << inode->num_blocks << endl;
    cout << "\tLink Count: " << inode->link_count;
    if (inode->free0_file1_dir2 == 2) {
        cout << "\tType: Directory" << endl;
    } else if (inode->free0_file1_dir2 == 1) {
        cout << "\tType: File" << endl;
    }

    cout << "\tInode: " << inode_id << endl;
    rawtime = inode->last_mod_ts;
    timeinfo = localtime(&rawtime);
    strftime(cstr_time,100,"%X %x",timeinfo);
    cout << "\tLast Modified: " << cstr_time << endl;

    return 0;
}

int FileSystem::Ls() {
    Directory *cur_dir;
    map <string, unsigned int>::iterator it;

    cur_dir = new Directory(this, cur_dir_inode_id);
    if (cur_dir == NULL) {
        printf( "Problem getting FS directory information");
        return -1;
    }

    for (it = cur_dir->entries.begin(); it != cur_dir->entries.end(); it++) {
        //print sub-dir or file name
        printf("%s\n", it->first.c_str());
    }

    delete cur_dir;

    return 0;
}

int FileSystem::Cat(string filename) {

    unsigned int fp1;
    char* buffer = (char*) malloc(2);
    File* f;

    buffer[1]= '\0';
    
    map<unsigned int, File*>::iterator it;
    
    fp1 = Open(filename, "r");

    if (open_files.count(fp1) > 0) {
        it = open_files.find(fp1);

        f = (*it).second;

        if (f->inode->free0_file1_dir2 == 2) {
            printf( "cat: %s: Is a directory\n", filename.c_str());
            Close(fp1);
            return -1;
        }

        f->Seek(0);

        if (f != NULL) {
            while (f->Read(1, buffer) > 0) {
                cout << buffer[0];
            }
        } else {
            Close(fp1);
            return -1;
        }
    }
    
    Close(fp1);
    return 0;
}

int FileSystem::Cp(string src, string dest) {

    int fp1;
    int fp2;
    char* buffer = (char*) malloc(2);
    File* f;
    File* f2;
    unsigned int temp;
    unsigned int inode_id;
    int r;

    buffer[1]= '\0';
    
    map<unsigned int, File*>::iterator it;
    map<unsigned int, File*>::iterator it2;

    //first, make sure the destination does not exist
    r = inode_id_from_name(dest, &inode_id);
    if (r == 0) {
        printf( "Error FileSystem::Cp - destination file already exists ('%s')\n", dest.c_str());
        return -1;
    }

    fp1 = Open(src, "r");
    if(fp1 == -1){
        return -1;
    }    
    //don't create output file until we are sure the input file could be opened
    fp2 = Open(dest, "w");
    if(fp2 == -1){
        Close(fp1);
        return -1;
    }

    if (open_files.count(fp1) > 0 && open_files.count(fp2) > 0) {
        it = open_files.find(fp1);
        it2 = open_files.find(fp2);

        f = (*it).second;
        f2 = (*it2).second;

        f->Seek(0);
        f2->Seek(0);
        temp = 0;

        if (f != NULL && f2 != NULL) {
            while (f->Read(1, buffer) > 0) {
                temp++;

                f2->Seek((temp - 1));
                f2->Write(1, buffer);

                f->Seek(temp);
            }
        } else {
            Close(fp1);
            Close(fp2);
            return -1;
        }
    }

    Close(fp1);
    Close(fp2);

    return 0;
}

/*
Desc:
    Recursively prints the directories and files starting with the current directory.
    Ignores ".."
Args:
    dir defaults to NULL for the initial user's call
    indent_lvl starts at 0
 */
void FileSystem::Tree(Directory * cur_dir, unsigned int indent_lvl) {

    Directory * next_dir;
    map <string, unsigned int>::iterator it; //key = filename, val = inode number
    Inode * inode;
    struct tm * timeinfo;
    time_t rawtime;
    char cstr_time[100];

    if (cur_dir == NULL) {
        cur_dir = new Directory(this, cur_dir_inode_id);
    }

    for (it = cur_dir->entries.begin(); it != cur_dir->entries.end(); it++) {
        //get inode
        inode = inodes[it->second];

        //don't print out ..
        if (it->first == "..") continue;

        //print sub-dir or file name
        printf("%*s%s - ", indent_lvl * 2, (char *) "", it->first.c_str());

        rawtime = inode->last_mod_ts;
        timeinfo = localtime(&rawtime);
        strftime(cstr_time,100,"%X %x",timeinfo);
        cout << "Last Modified: " << cstr_time << " - Size: " << inode->size << endl;

        if (inode->free0_file1_dir2 == 2) {
            //print that directory
            next_dir = new Directory(this, it->second);
            Tree(next_dir, indent_lvl + 1);
        }

    }

    delete cur_dir;

    return;
}

/*
Desc:
    Imports a file from the user's system to the current directory of the simulated file system.
    The destination file cannot already exist in the simulated file system (consistent with the requirements for cp).
Args:
    src_path - path to the file on the user's real system
    dest_name - name given to the file in this fake file system (placed in current directory)
Returns:
    0 on success
    -1 on failure
 */
int FileSystem::Import(string src_path, string dest_name) {

    ifstream if_in;
    char buf[1024];
    streamsize ss;
    File * dest_file;
    int r;
    size_t szt;
    unsigned int inode_id;
    unsigned int num_written;

    //the destination file goes in the current directory, so make sure no '/' in name
    szt = dest_name.find('/');
    if (szt != string::npos) {
        printf( "Error FileSystem::Import - destination filename contains '/' ('%s')\n", dest_name.c_str());
        return -1;
    }

    //open file to import
    if_in.open(src_path.c_str(), ios::binary);
    if (if_in.is_open() == false) {
        printf( "Error FileSystem::Import - source file not found ('%s')\n", src_path.c_str());
        return -1;
    }

    //make sure the destination file does not exist
    r = inode_id_from_name(dest_name, &inode_id);
    if (r == 0) {
        printf( "Error FileSystem::Import - destination file already exists ('%s')\n", dest_name.c_str());
        return -1;
    }

    //create destination file ('or_get' doesn't apply because we already checked that it doesn't exist)
    dest_file = create_or_get_file(dest_name);
    if (dest_file == NULL) {
        return -1;
    }

    //read file to import
    while (if_in.eof() == false) {
        if_in.read(buf, sizeof (buf));
        //see how many bytes we read
        ss = if_in.gcount();
        if (ss == 0) {
            break;
        }

        //write these to our fake file system
        num_written = dest_file->Write(ss, buf);
        if (num_written != (unsigned int) ss) {
            printf( "Error FileSystem::Import - File::Write only wrote %u of %u bytes\n", num_written, (unsigned int) ss);
            delete dest_file;
            return -1;
        }
    }
    if_in.close();

    delete dest_file;
    return 0;
}

/*
Desc:
    Exports a file from the current directory of the simulated file system to the user's system.
    If the destination file exists, it is overwritten.
Args:
    src_name - name of the file in the simulated filesystem (in the current directory)
    dest_path - full path to the file on the user's real system
Returns:
    0 on success
    -1 on failure
 */
int FileSystem::Export(string src_name, string dest_path) {

    ofstream of_dest;
    unsigned int inode_id;
    int r;
    File * src_file;
    char buf[1024];
    unsigned int num_read;

    //make sure the source file exists
    r = inode_id_from_name(src_name, &inode_id);
    if (r != 0) {
        printf( "Error FileSystem::Export - source file does not exist ('%s')\n", src_name.c_str());
        return -1;
    }

    //get the file
    src_file = new File(this, inode_id);

    //get ready to output to a real file, (truncates it)
    of_dest.open(dest_path.c_str(), ios_base::out | ios_base::trunc);
    if (of_dest.is_open() == false) {
        printf( "Error FileSystem::Export - could not open destination file '%s'\n", dest_path.c_str());
        delete src_file;
        return -1;
    }

    //read from the fake file, write to the real file
    while ((num_read = src_file->Read(sizeof (buf), buf)) != 0) {
        //write to real file
        of_dest.write(buf, num_read);
        if (of_dest.bad() == true) {
            printf( "Error FileSystem::Export - write() failed\n");
            of_dest.close();
            delete src_file;
            return -1;
        }
    }

    of_dest.close();
    delete src_file;

    return 0;
}

/*****************
Start private methods
 ******************/

Directory * FileSystem::create_new_directory() {

    Directory * new_dir;
    Inode * free_inode;
    unsigned int free_inode_id;

    free_inode = get_free_inode(&free_inode_id);
    if (free_inode == NULL) {
        return NULL;
    }

    new_dir = new Directory(this, free_inode_id);
    free_inode->free0_file1_dir2 = 2;
    free_inode->last_mod_ts = time(NULL);
    write_inode(new_dir);

    return new_dir;

}

/*
Desc:
    Pass this a complete filename (including directories).
    If the file already exists, it gets returned.
    Otherwise, it is created (assuming the parent directory is valid).
Args:
    filename - complete filename (including path)
Returns:
    File * on success
    NULL on error
 */
File * FileSystem::create_or_get_file(string filename) {

    unsigned int inode_id;
    int r;
    File * f;
    Inode * free_inode;
    string str_file;
    string str_parent_dir;
    Directory * parent_dir;

    r = inode_id_from_name(filename, &inode_id);
    if (r == 0) {
        //file exists
        f = new File(this, inode_id);
        return f;
    }

    //file does not exist
    free_inode = get_free_inode(&inode_id);
    if (free_inode == NULL) {
        return NULL;
    }

    f = new File(this, inode_id);
    f->inode->free0_file1_dir2 = 1;
    f->inode->last_mod_ts = time(NULL);
    write_inode(f);

    //add the new file to the parent directory
    r = split_parent_child(filename, str_parent_dir, str_file);
    if (r != 0) {
        delete f;
        return NULL;
    }

    parent_dir = get_dir(str_parent_dir);
    if (parent_dir == NULL) {
        delete f;
        return NULL;
    }

    r = parent_dir->AddEntry(str_file, inode_id);
    if (r != 0) {
        delete parent_dir;
        delete f;
        return NULL;
    }

    delete parent_dir;
    return f;

}

/*
Returns an unused inode.
Sets inode_id to the id of the free inode.
Returns:
    Inode * on success
    NULL on error (such as no more free inodes)
 */
Inode * FileSystem::get_free_inode(unsigned int * inode_id) {

    unsigned int i;
    Inode * tmp_inode;

    //find an available inode
    for (i = 0; i < Settings::NumInodes; i++) {
        tmp_inode = inodes[i];
        if (tmp_inode->free0_file1_dir2 == 0) {
            *inode_id = i;
            return tmp_inode;
        }
    }

    printf( "Error File::File - no free inodes\n");
    return NULL;

}

/*
re-initializes all the inode's values to defaults, sets this inode as 'free' so that it can be used for a new file
This is called at least twice: when we make a new filesystem and when a file is unlinked
 */
int FileSystem::set_inode_to_free(unsigned int inode_id) {

    Inode * tmp_inode;
    int r;

    tmp_inode = inodes[inode_id];

    tmp_inode->free0_file1_dir2 = 0;
    tmp_inode->link_count = 0;
    tmp_inode->size = 0;
    tmp_inode->num_blocks = 0;
    tmp_inode->last_mod_ts = 0;

    /*
    The data pointers are set to 0 just so valgrind stops complaining 
    that uninitialized data gets written to a file.  
    A value of 0 does not mean the pointer is not used. A file's
    size is used to determine which pointers are set.
     */
    bzero(tmp_inode->ptrs_dir, sizeof (tmp_inode->ptrs_dir));
    bzero(tmp_inode->ptrs_indir1, sizeof (tmp_inode->ptrs_indir1));
    bzero(tmp_inode->ptrs_indir2, sizeof (tmp_inode->ptrs_indir2));

    r = write_inode(inode_id, tmp_inode);
    if (r != 0) {
        return -1;
    }

    return 0;
}

/*
Desc:
    Sets str_parent to all but the last folder or the filename.
    Sets str_child to the last folder or filename.
    Note: if '/' is passed in, str_parent becomes "/" and str_child is set to ""
Args:
    str_full - a directory or file path
Returns:
    0 on success
    -1 on error
 */
int FileSystem::split_parent_child(string str_full, string & str_parent, string & str_child) {

    int i;
    unsigned int pos;

    //handle root directory case
    if ((str_full.length() == 1) && (str_full[0] == '/')) {
        str_parent = "/";
        str_child = "";
        return 0;
    }

    //remove trailing slashes
    i = str_full.length() - 1;
    while (str_full[i] == '/') {
        str_full.erase(str_full.end() - 1);
        i--;
    }
    //cout << "\t removed trailing slashes: '" << str_full << "'" << endl;

    //find the last '/'
    pos = str_full.rfind('/');
    if (pos == string::npos) {
        //no slash found, putting new directory in the current directory
        str_parent = "";
        str_child = str_full;
    } else {
        /*
        Everything up to and included '/' is the parent directory's path.
        Everything after '/' is the new directory's name.
         */
        str_parent = str_full.substr(0, pos + 1);
        str_child = str_full.substr(pos + 1);
    }

    //    cout << "\t parent dir = '" << str_parent << "'" << endl;
    //    cout << "\t new dir = '" << str_child << "'" << endl;
    if (str_child.length() == 0) {
        printf( "Error FileSystem::split_parent_child - no child in original path: '%s'\n", str_full.c_str());
        return -1;
    }

    return 0;

}

/*
Desc:
    Pass in a user-supplied path string, get the directory object
corresponding to the innermost directory.
    Example:
    input a/b/c
    output Directory * for 'c'
    '', '.', './' returns the current directory
    '/' or '////' (any number of just slashes) returns the root directory
Returns:
    the directory on success
    null on error
 */
Directory * FileSystem::get_dir(string str_dir) {

    int i;
    vector <string> vec_dirs;
    Directory * dir;
    unsigned int dir_id;
    int num_dirs;
    int r;

    //cout << "getting dir '" << str_dir << "'" << endl;

    if (str_dir.length() == 0) {
        //return current directory
        dir_id = cur_dir_inode_id;
        dir = new Directory(this, dir_id);
        //cout << "\t returned current dir" << endl;
        return dir;
    }

    //remove trailing slashes
    i = str_dir.length() - 1;
    while (str_dir[i] == '/') {
        str_dir.erase(str_dir.end() - 1);
        i--;
    }
    //cout << "\t removed trailing slashes: '" << str_dir << "'" << endl;

    if (str_dir.length() == 0) {
        //return root directory
        dir_id = root_dir_inode_id;
        dir = new Directory(this, dir_id);
        //cout << "\t returned root dir" << endl;
        return dir;
    }

    /*
    str_dir is something like:
        "./a/b/c",
        "/a/b/c",
        "a",
        "/a",
        "./b",
        "a/b/c"
     */
    num_dirs = Utils::SplitString(str_dir, '/', vec_dirs);
    /*
    after split:
        "./a/b/c" => ., a, b, c
        "/a/b/c" => "", a, b, c
        "a" => a
        "/a" => "", a
        "./b" => ., b
        "a/b/c" => a, b, c
        "../a/b/c" => .., a, b, c
     */

    //start at either the root directory, or the current directory
    if (vec_dirs[0] == "") {
        //start in root
        dir_id = root_dir_inode_id;
    } else {
        dir_id = cur_dir_inode_id;
    }
    dir = new Directory(this, dir_id);

    i = 0;
    while (i < num_dirs) {
        if (vec_dirs[i] == "" || vec_dirs[i] == ".") {
            i++;
            continue;
        }
        r = dir->CdChildDirectory(vec_dirs[i]);
        if (r != 0) {
            delete dir;
            return NULL;
        }
        i++;
    }

    return dir;
}

/*
Args:
    Finds the first free data block, loads it via the cache,
    marks it used, returns it.
Returns:
    DataBlock *
 */
DataBlock * FileSystem::get_free_data_block() {

    unsigned int i;
    DataBlock * data_block;
    int r;

    for (i = 0; i < Settings::NumDataBlocks - 1; i++) {
        if (superblock[i] == '\0') {
            //this one is avail

            //load using cache
            data_block = cache->GetDataBlock(i);
            //mark it used
            superblock[i] = '1';
            r = write_superblock();
            if (r != 0) {
                delete data_block;
                return NULL;
            }
            return data_block;
        }
    }

    //we didn't find one
    printf( "Error FileSystem::get_free_data_block - no more free data blocks\n");
    return NULL;

}

/*
Call this whenever the superblock is updated
 */
int FileSystem::write_superblock() {

    int r;

    r = fseek(fs_file, 0, SEEK_SET);
    if (r != 0) {
        printf( "Error FileSystem::write_superblock - fseek(0)\n");
        return -1;
    }

    r = fwrite(superblock, Settings::NumDataBlocks, 1, fs_file);
    if (r != 1) {
        printf( "Error FileSystem::write_superblock - fwrite(superblock)\n");
        return -1;
    }

    return 0;
}

/*
Actually write to the fs_file.
 */
int FileSystem::write_inode(File * file) {

    unsigned int offset;
    unsigned int ul;
    int r;

    if (file->inode == NULL) {
        printf( "Error FileSystem::write_inode - inode is NULL\n");
        return -1;
    }

    //calculate correct offset
    offset = Settings::NumDataBlocks + sizeof (Inode)*(file->inode_id);

    r = fseek(fs_file, offset, SEEK_SET);
    if (r != 0) {
        printf( "Error FileSystem::write_inode - fseek failed\n");
        return -1;
    }

    ul = fwrite(file->inode, sizeof (Inode), 1, fs_file);
    if (ul != 1) {
        printf( "Error FileSystem::write_inode - fwrite failed\n");
        return -1;
    }

    return 0;
}

/*
Does the same thing as the previous function, but lets us just pass the inode id and the Inode*
 */
int FileSystem::write_inode(unsigned int inode_id, Inode * inode) {

    unsigned int offset;
    unsigned int ul;
    int r;

    //calculate correct offset
    offset = Settings::NumDataBlocks + sizeof (Inode)*(inode_id);

    r = fseek(fs_file, offset, SEEK_SET);
    if (r != 0) {
        printf( "Error FileSystem::write_inode - fseek failed\n");
        return -1;
    }

    ul = fwrite(inode, sizeof (Inode), 1, fs_file);
    if (ul != 1) {
        printf( "Error FileSystem::write_inode - fwrite failed\n");
        return -1;
    }

    return 0;
}

/*
Desc:
    data_block should already be declared and given an id.
    This uses the data_block id to calculate the file offset.
    It reads the entire block from the file and puts it in
    data_block->data (which is pre-allocated by DataBlock).
Returns:
    0 on success
    -1 on error
 */
int FileSystem::read(DataBlock * data_block) {

    int r;
    unsigned int offset;
    unsigned int num_read;

    //offset to this data block
    offset = Settings::NumDataBlocks + Settings::NumInodes * sizeof (Inode) + Settings::BlockSize * data_block->id;
    r = fseek(fs_file, offset, SEEK_SET);
    if (r != 0) {
        printf( "Error FileSystem::read - fseek(FILE,%u,SEEK_SET)\n", offset);
        return -1;
    }

    //read the block
    num_read = fread(data_block->Data, Settings::BlockSize, 1, fs_file);
    if (num_read != 1) {
        return -1;
    }

    return 0;
}

/*
Desc:
    Actually write to the fs_file.
Args:
    offset - # bytes offset from the beginning of the DataBlock
    size - # bytes to write (the DataBlock::Write method does the error checking for this)
Returns:
    0 on success
    -1 on error
 */
int FileSystem::write(DataBlock * data_block, unsigned int offset, unsigned int size) {

    int r;
    unsigned int file_offset;
    unsigned int num_wrote;

    //offset to this data block
    file_offset = Settings::NumDataBlocks + Settings::NumInodes * sizeof (Inode) + Settings::BlockSize * data_block->id + offset;
    r = fseek(fs_file, file_offset, SEEK_SET);
    if (r != 0) {
        printf( "Error FileSystem::write - fseek(FILE,%u,SEEK_SET)\n", file_offset);
        return -1;
    }

    //write the block
    num_wrote = fwrite(data_block->Data + offset, size, 1, fs_file);
    if (num_wrote != 1) {
        return -1;
    }

    return 0;
}

/*
Desc:
    If a file exists: returns 0 and sets inode_id to the inode id.
    If it doesn't exist: returns -1
    Can be used to check if a file exists. 
    If you need an actual reference to a file just do:
    inode_id_from_name(path,&id);
    file = new File(this,id)
Args:
    filename - full filename (includes path)
Returns:
    0 if file exists
    -1 if file does not exist
 */
int FileSystem::inode_id_from_name(string filename, unsigned int * inode_id) {

    string str_file;
    string str_parent_dir;
    Directory * parent_dir;
    map <string, unsigned int>::iterator it;
    int i;

    i = split_parent_child(filename, str_parent_dir, str_file);
    if (i != 0) {
        return -1;
    }

    //handle special root directory case
    if (str_parent_dir == "/" && str_file == "") {
        *inode_id = root_dir_inode_id;
        return 0;
    }

    parent_dir = get_dir(str_parent_dir);
    if (parent_dir == NULL) {
        return -1;
    }
    it = parent_dir->entries.find(str_file);
    if (it == parent_dir->entries.end()) {
        //file does not exist
        delete parent_dir;
        return -1;
    }

    *inode_id = it->second;
    delete parent_dir;
    return 0;

}
